package com.king.alg;

import com.king.pojo.Point;

import java.util.*;

public class PolygonAlgorithms {

    /**
     * 创建多边形
     *
     * @param points            多边形顶点坐标
     * @param excludePointIndex 多边形中被排除的点的索引
     * @return 多边形
     */
    public static List<Point> createPolygonExclude(List<Point> points, int excludePointIndex) {
        List<Point> result = new LinkedList<>();
        int len = points.size();
        for (int i = 0; i < len; i++) {
            if (i != excludePointIndex) {
                result.add(points.get(i));
            }
        }
        return result;
    }

    /**
     * 创建多边形
     *
     * @param pointList 多边形顶点坐标
     * @param indexes   索引列
     * @return 多边形
     */
    public static List<Point> createPolygonInclude(List<Point> pointList, List<Integer> indexes) {
        List<Point> result = new LinkedList<>();
        indexes.forEach(i -> result.add(pointList.get(i)));
        return result;
    }

    /**
     * 求 由点p3向y轴正方向引出的射线 与 线段p1p2 的交点个数
     *
     * @param p1 线段端点
     * @param p2 线段端点
     * @param p3 点
     * @return 交点个数
     */
    public static int countIntersects(Point p1, Point p2, Point p3) {
        if (p1.getX() == p2.getX()) {
            if (p1.getX() == p3.getX()) {
                return 1;
            }
        }
        Point left = p1;
        Point right = p2;
        if (p2.getX() < p1.getX()) {
            left = p2;
            right = p1;
        }
        double x1 = left.getX();
        double y1 = left.getY();
        double x2 = right.getX();
        double y2 = right.getY();
        double x3 = p3.getX();
        double y3 = p3.getY();
        if (x3 < x1 || x3 > x2 || y3 > Math.max(y1, y2)) {
            return 0;
        }
        if (y2 + (y1 - y2) / (x1 - x2) * (x3 - x2) >= y3) {
            return 1;
        }
        return 0;
    }

    /**
     * 外阔或内缩点
     *
     * @param p1           p1
     * @param p2           p2
     * @param p3           p3
     * @param d            扩的距离
     * @param isExtendFlag 外阔：true； 内缩：false
     * @return 点
     */
    public static Point extendOrIntroversionPoint(Point p1, Point p2, Point p3, double d, boolean isExtendFlag) {
        double p3p2p1p2 = GeometryAlgorithms.outerProduct(p3, p2, p1, p2);
        double p1p2norm = GeometryAlgorithms.norm(p1, p2);
        double p3p2norm = GeometryAlgorithms.norm(p3, p2);
        int isExtend = isExtendFlag ? 1 : -1;
        double k = isExtend * d / Math.abs(p3p2p1p2);
        double[] p3p2 = GeometryAlgorithms.getVector(p3, p2);
        double[] p1p2 = GeometryAlgorithms.getVector(p1, p2);

        return new Point(
                p2.getX() + k * (p1p2norm * p3p2[0] + p3p2norm * p1p2[0]),
                p2.getY() + k * (p1p2norm * p3p2[1] + p3p2norm * p1p2[1])
        );
    }

    /**
     * 判断点point是否在多边形内部
     *
     * @param pointList 多边形顶点集
     * @param point     点
     * @return 点point在多边形内部：true
     */
    public static boolean isInner(List<Point> pointList, Point point) {
        int count = 0;
        int length = pointList.size();
        for (int i = 0; i < length; i++) {
            count += countIntersects(pointList.get(i), pointList.get((i + 1) % length), point);
        }
        return count % 2 == 1;
    }

    /**
     * 判断某个点是否是凸点
     *
     * @param pointList 多边形顶点集
     * @param index     点索引
     * @return 是否是凸点
     */
    public static boolean isConvex(List<Point> pointList, int index) {
        return !isInner(createPolygonExclude(pointList, index), pointList.get(index));
    }

    /**
     * 得到给定多边形等距外阔 dis 距离的新多边形
     *
     * @param pointList 多边形顶点集
     * @param dis       距离
     * @return 新多边形顶点集
     */
    public static List<Point> extendPolygon(List<Point> pointList, double dis) {
        List<Point> result = new LinkedList<>();
        int length = pointList.size();

        for (int i = 0; i < length; i++) {
            result.add(extendOrIntroversionPoint(
                    pointList.get((i - 1 + length) % length),
                    pointList.get(i),
                    pointList.get((i + 1) % length),
                    dis,
                    isConvex(pointList, i)
            ));
        }

        return result;
    }

    public static Point minDistancePoint2Polygon(List<Point> pointList, Point point) {
        double minDis = Double.MAX_VALUE;
        Point minPoint = null;
        int length = pointList.size();

        for (int i = 0; i < length; i++) {
            Point A = pointList.get(i);
            Point B = pointList.get((i + 1) % length);
            Point currPoint = GeometryAlgorithms.minDistancePoint2LineOrLineSegment(A, B, point, true);
            double currDis = GeometryAlgorithms.norm(point, currPoint);
            if (currDis < minDis) {
                minDis = currDis;
                minPoint = currPoint;
            }
        }

        return minPoint;
    }

    public static Point maxDistancePoint2Polygon(List<Point> pointList, Point point) {
        double maxDis = 0;
        Point maxPoint = null;
        int length = pointList.size();

        for (int i = 0; i < length; i++) {
            Point currPoint = pointList.get(i);
            double currDis = GeometryAlgorithms.norm(point, currPoint);
            if (currDis > maxDis) {
                maxDis = currDis;
                maxPoint = currPoint;
            }
        }

        return maxPoint;
    }

    public static Point middleDistancePoint2Polygon(List<Point> pointList, Point point) {
        Point maxPoint = maxDistancePoint2Polygon(pointList, point);
        Point minPoint = minDistancePoint2Polygon(pointList, point);
        return new Point((maxPoint.getX() + minPoint.getX()) / 2, (maxPoint.getY() + minPoint.getY()) / 2);
    }

    /**
     * 点point到多边形的最短距离
     *
     * @param pointList 多边形顶点集
     * @param point     点
     * @return 点point到多边形的最短距离
     */
    public static double minDistance2Polygon(List<Point> pointList, Point point) {
        double minDis = Double.MAX_VALUE;
        int length = pointList.size();

        for (int i = 0; i < length; i++) {
            double curr = GeometryAlgorithms.minDistance2LineOrLineSegment(pointList.get(i), pointList.get((i + 1) % length), point, true);
            if (curr < minDis) {
                minDis = curr;
            }
        }

        return minDis;
    }

    /**
     * 根据点列获得原始索引
     *
     * @param coordinatePointList 点列
     * @return 索引
     */
    public static List<Integer> getOriIndexesFromPointList(List<Point> coordinatePointList) {
        List<Integer> result = new LinkedList<>();
        coordinatePointList.forEach(o -> result.add(o.getOriIndex()));
        return result;
    }

    /**
     * 根据点列获得等距索引
     *
     * @param coordinatePointList 点列
     * @return 索引
     */
    public static List<Integer> getEquIndexesFromPointList(List<Point> coordinatePointList) {
        List<Integer> result = new LinkedList<>();
        coordinatePointList.forEach(o -> result.add(o.getEquIndex()));
        return result;
    }

}
